import {
  _decorator,
  Component,
  Node,
  input,
  screen,
  Input,
  EventKeyboard,
  KeyCode,
  Button,
  RigidBody,
  Vec3,
  Vec4,
  Camera,
  EventTouch,
  EventMouse,
  Quat,
  misc,
  Vec2,
  PhysicMaterial,
  PhysicsSystem,
  tween,
  sys,
} from "cc";
const { ccclass, property } = _decorator;
enum MouseButtons {
  LeftButton = 0,
  MiddleButton = 1,
  RightButton = 2,
  ReturnButton = 3,
}

interface CameraInfo {
  worldPosition: Vec3;
  worldRotation: Quat;
  fov: number;
}
@ccclass("CameraController")
export class CameraController extends Component {
  private keyFlgs: { [key: string]: boolean } = {};
  private camera: Camera;

  @property
  public moveSpeed: number = 0.1;

  @property
  public minFov: number = 0.1;

  @property
  public maxFov: number = 160;

  @property
  public fovAjustSpeed: number = 10;

  @property
  // 垂直翻转最大角度
  public maxVerticalAngle: number = -10;

  @property
  // 垂直翻转最小角度
  public minVerticalAngle: number = -80;

  @property(Node)
  public lightNode: Node;

  private defaultInfo: CameraInfo;

  onLoad() {
    if (this.isTouchable()) {
      input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
      input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
      input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
      input.on(Input.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
    } else {
      input.on(Input.EventType.MOUSE_DOWN, this.onMouseDown, this);
      input.on(Input.EventType.MOUSE_MOVE, this.onMouseMove, this);
      input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
      input.on(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
      input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
      input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
    }
  }

  start() {
    this.camera = this.node.getComponent(Camera);
    this.defaultInfo = {
      worldPosition: this.node.getWorldPosition(),
      worldRotation: this.node.getWorldRotation(),
      fov: this.camera.fov,
    };
  }

  public onDestroy(): void {
    if (this.isTouchable()) {
      input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
      input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
      input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this);
      input.off(Input.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
    } else {
      input.off(Input.EventType.MOUSE_DOWN, this.onMouseDown, this);
      input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this);
      input.off(Input.EventType.MOUSE_MOVE, this.onMouseMove, this);
      input.off(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
      input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
      input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
    }
  }

  private startRotate: boolean = false;
  private startRotateWorldPosition: Vec3;

  private clickCount = 0;
  // 连续点击的间隔时间
  private clickTimeoutMs = 200;
  private clickTimeoutHandler = 0;

  private onMouseDown(e: EventMouse) {
    this.clickCount++;
    clearTimeout(this.clickTimeoutHandler);
    this.clickTimeoutHandler = setTimeout(() => {
      this.clickCount = 0;
    }, this.clickTimeoutMs);

    if (e.getButton() == MouseButtons.LeftButton) {
      this.startRotate = true;
      this.startRotateWorldPosition = this.getScreeCenterWorldPosition();
    }
  }

  private isTouchable() {
    if (sys.os == sys.OS.WINDOWS || sys.os == sys.OS.OSX) {
      return false;
    }
    return true;
  }

  private onMouseUp(e: EventMouse) {
    if (e.getButton() == MouseButtons.LeftButton) {
      this.startRotate = false;
    }
    if (this.clickCount == 2) {
      this.onMouseDoubleClick(e);
    }
  }

  private onMouseDoubleClick(e: EventMouse) {
    // console.log('double click');
    const location = e.getLocation();
    const ray = this.camera.screenPointToRay(location.x, location.y);
    if (PhysicsSystem.instance.raycastClosest(ray)) {
      const result = PhysicsSystem.instance.raycastClosestResult;
      this.startRotateWorldPosition = result.collider.node.getWorldPosition();
      // 以对象为旋转轴
      this.rotateHorizontal(this.startRotateWorldPosition, 0);
    } else {
      // 以远点为旋转轴
      this.reset();
    }
  }

  public reset(animationSeconds: number = 1) {
    this.node.setWorldRotation(this.defaultInfo.worldRotation);

    tween(this.node)
      .to(animationSeconds, {
        worldPosition: this.defaultInfo.worldPosition,
        worldRotation: this.defaultInfo.worldRotation,
      })
      .start();
    tween(this.camera)
      .to(animationSeconds, { fov: this.defaultInfo.fov })
      .start();
  }

  private onMouseMove(e: EventMouse) {
    const button = e.getButton();
    if (button == MouseButtons.MiddleButton) {
      const vec3 = new Vec3(
        -e.getDeltaX() * this.moveSpeed,
        -e.getDeltaY() * this.moveSpeed,
        0
      );
      this.move(vec3);
    } else if (button == MouseButtons.RightButton) {
      const vec3 = new Vec3(
        -e.getDeltaX() * this.moveSpeed,
        -e.getDeltaY() * this.moveSpeed,
        0
      );
      this.move(vec3);
    } else if (button == MouseButtons.LeftButton) {
      if (this.startRotate) {
        let speed = 0.01;
        // 水平方向的旋转
        this.rotateHorizontal(
          this.startRotateWorldPosition,
          -e.getDeltaX() * speed
        );

        // 垂直方向的旋转
        this.rotateVertical(
          this.startRotateWorldPosition,
          e.getDeltaY() * speed
        );
      }
    }
  }

  public getScreeCenterWorldPosition() {
    const centerPoint: Vec2 = new Vec2(
      screen.windowSize.width / 2,
      screen.windowSize.height / 2
    );
    const ray = this.camera.screenPointToRay(centerPoint.x, centerPoint.y);

    const b = new Vec3();
    // public static scaleAndAdd<Out extends IVec3Like> (out: Out, a: IVec3Like, b: IVec3Like, scale: number) {
    //     out.x = a.x + b.x * scale;
    //     out.y = a.y + b.y * scale;
    //     out.z = a.z + b.z * scale;
    //     return out;
    // }
    Vec3.normalize(b, ray.d);
    const scale = -ray.o.y / b.y;
    const x = ray.o.x + b.x * scale;
    const z = ray.o.z + b.z * scale;
    return new Vec3(x, 0, z);
  }

  private onMouseWheel(e: EventMouse) {
    const scrollY = e.getScrollY();
    if (this.keyFlgs[KeyCode.CTRL_LEFT]) {
      if (scrollY != 0) {
        const speed = 0.01;
        const vec3 = new Vec3(0, scrollY > 0 ? speed : -speed, 0);
        this.move(vec3);
      }
    } else {
      if (scrollY > 0) {
        this.zoomIn();
      } else if (scrollY < 0) {
        this.zoomOut();
      }
    }

    const scrollX = e.getScrollX();
    if (scrollX != 0) {
      const speed = 0.01;
      const vec3 = new Vec3(scrollX > 0 ? speed : -speed, 0, 0);
      this.move(vec3);
    }
  }

  private onTouchStart(e: EventTouch) {
    const touches = e.getAllTouches();
    if (touches.length == 1) {
      this.startRotateWorldPosition = this.getScreeCenterWorldPosition();
    } else if (touches.length == 2) {
      this.touchDistance = Vec2.distance(
        touches[0].getLocation(),
        touches[1].getLocation()
      );
    }
  }

  private touchDistance: number | null = null;

  public rotateSpeedByTouch = 0.01;
  public sacleMinDistanceByTouch = 50;
  public moveSpeedByTouch = 0.002;

  private onTouchMove(e: EventTouch) {
    const touches = e.getAllTouches();
    if (touches.length == 1) {
      let delta = e.getDelta();
      let horizontal = -delta.x * this.rotateSpeedByTouch;
      let vertical = delta.y * this.rotateSpeedByTouch;
      // 计算水平方向的偏移量
      this.rotateHorizontal(this.startRotateWorldPosition, horizontal);

      // 计算垂直方向的偏移量
      this.rotateVertical(this.startRotateWorldPosition, vertical);
    } else if (touches.length == 2) {
      if (this.touchDistance == null) {
        this.touchDistance = Vec2.distance(
          touches[0].getLocation(),
          touches[1].getLocation()
        );
      } else {
        const newTouchDistance = Vec2.distance(
          touches[0].getLocation(),
          touches[1].getLocation()
        );

        // 缩放画面
        const deltaDistance = newTouchDistance - this.touchDistance;
        if (Math.abs(deltaDistance) >= this.sacleMinDistanceByTouch) {
          if (newTouchDistance > this.touchDistance) {
            this.zoomIn();
          } else if (newTouchDistance < this.touchDistance) {
            this.zoomOut();
          }

          this.touchDistance = newTouchDistance;
        }

        // 移动画面
        const delta1 = touches[0].getDelta();
        const delta2 = touches[1].getDelta();
        const deltaX = this.getDeltaValue(delta1.x, delta2.x);
        const deltaY = this.getDeltaValue(delta1.y, delta2.y);
        const vec3 = new Vec3(
          -deltaX * this.moveSpeedByTouch,
          -deltaY * this.moveSpeedByTouch,
          0
        );
        this.move(vec3);
      }
    }
  }

  private getDeltaValue(val1: number, val2: number): number {
    // 如果两个触点的轴变化不是同一方向的，忽略移动
    if (val1 * val2 <= 0) {
      return 0;
    }

    // 获取绝对值小的数
    if (Math.abs(val1) < Math.abs(val2)) {
      return val1;
    }
    return val2;
  }

  private onTouchEnd(e: EventTouch) {
    const touches = e.getAllTouches();
    if (touches.length != 2) {
      this.touchDistance = null;
    }
  }
  private onTouchCancel(e: EventTouch) {
    const touches = e.getAllTouches();
    if (touches.length != 2) {
      this.touchDistance = null;
    }
  }

  update(deltaTime: number) {
    if (this.lightNode) {
      this.lightNode.setWorldPosition(this.camera.node.getWorldPosition());
      this.lightNode.setWorldRotation(this.camera.node.getWorldRotation());
    }
  }

  onKeyDown(e: EventKeyboard) {
    this.keyFlgs[e.keyCode] = true;
  }
  onKeyUp(e: EventKeyboard) {
    this.keyFlgs[e.keyCode] = false;
    delete this.keyFlgs[e.keyCode];
  }

  public rotateVertical(targetWorldPosition: Vec3, angle) {
    let { y } = this.node.eulerAngles;
    let radians = misc.degreesToRadians(y);
    let axis = new Vec3(Math.cos(radians), 0, -Math.sin(radians));
    // 计算垂直方向的偏移量
    this.rotateAround(this.node, targetWorldPosition, axis.normalize(), angle);
  }

  public rotateHorizontal(targetWorldPosition: Vec3, angle) {
    this.rotateAround(this.node, targetWorldPosition, new Vec3(0, 1, 0), angle);
  }

  private rotateAround(
    node: Node,
    targetWorldPosition: Vec3,
    axis: Vec3,
    angle: number
  ) {
    // 根据旋转轴和旋转角度构建四元数
    let quat = new Quat();
    Quat.fromAxisAngle(quat, axis, angle);

    // 计算旋转后的位置
    let position = new Vec3();
    Vec3.subtract(position, node.worldPosition, targetWorldPosition);
    Vec3.transformQuat(position, position, quat);
    Vec3.add(position, targetWorldPosition, position);

    // 根据旋转后的位置计算四元数
    let dir = new Vec3();
    Vec3.subtract(dir, position, targetWorldPosition);
    let rotation = new Quat();
    Quat.fromViewUp(rotation, dir.normalize(), Vec3.UP);

    // 根据四元数计算欧拉角 判断是否在需要的范围内
    let euler = new Vec3();
    rotation.getEulerAngles(euler);
    if (euler.x < this.minVerticalAngle || euler.x > this.maxVerticalAngle) {
      return;
    }

    // Vec3.transformQuat(position, position, this.node.getRotation());
    // 设置位置及旋转
    node.setWorldPosition(position);
    node.setWorldRotation(rotation);
  }

  public move(vec3: Vec3): void {
    const positon = this.node.getWorldPosition();
    Vec3.transformQuat(vec3, vec3, this.node.getRotation());
    positon.add(vec3);
    this.node.setWorldPosition(positon);
  }

  public ajustFovSpeed = 0.1;
  public ajustFovMin = 0.01;

  // 缩小
  public zoomOut() {
    const originFov = this.camera.fov;
    var ajustFov = (originFov - this.minFov) * this.ajustFovSpeed;

    if (ajustFov > 0) {
      this.setFov(originFov + ajustFov);
    }
  }

  // 放大
  public zoomIn() {
    const originFov = this.camera.fov;
    var ajustFov = (originFov - this.minFov) * this.ajustFovSpeed;

    if (ajustFov >= this.ajustFovMin) {
      this.setFov(originFov - ajustFov);
    }
  }

  public setFov(fov) {
    fov = Math.max(fov, this.minFov);
    fov = Math.min(fov, this.maxFov);
    this.camera.fov = fov;
  }

  public fovSpeed = 10;
}
